/*
 * Copyright 2019 xiaomaoguai.com All right reserved. This software is the
 * confidential and proprietary information of xiaomaoguai.com ("Confidential
 * Information"). You shall not disclose such Confidential Information and shall
 * use it only in accordance with the terms of the license agreement you entered
 * into with xiaomaoguai.com.
 */

package com.xiaomaoguai.core.next.tddl.sequence;

import app.myoss.cloud.core.constants.MyossConstants;
import app.myoss.cloud.core.exception.BizRuntimeException;
import app.myoss.cloud.mybatis.table.Sequence;
import app.myoss.cloud.mybatis.table.TableSequence;
import com.google.common.collect.Lists;
import com.taobao.tddl.client.sequence.exception.SequenceException;
import com.taobao.tddl.client.sequence.impl.GroupSequence;
import com.taobao.tddl.client.sequence.impl.GroupSequenceDao;
import com.xiaomaoguai.core.next.tddl.config.TddlMultiProperties.TddlProperty;
import com.xiaomaoguai.core.next.tddl.config.TddlProperties;
import com.xiaomaoguai.core.next.tddl.config.TddlSequenceProperties;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.config.RuntimeBeanReference;
import org.springframework.beans.factory.support.AbstractBeanDefinition;
import org.springframework.beans.factory.support.BeanDefinitionBuilder;
import org.springframework.beans.factory.support.DefaultListableBeanFactory;
import org.springframework.context.ApplicationContext;

import java.util.Collection;
import java.util.HashMap;
import java.util.Map;

/**
 * TDDL 序列工具类
 *
 * @author chenyao
 * @since 2018年6月4日 下午10:09:34
 */
@Slf4j
public class TddlSequenceUtils {

    /**
     * 构建 TDDL GroupSequenceDao 实例对象
     *
     * @param tddlAppName TDDL appName
     * @param tddlGroupName TDDL groupName
     * @param sequenceConfig TDDL Sequence 属性配置
     * @param init 是否进行初始化
     * @return TDDL GroupSequenceDao 实例对象
     */
    public static GroupSequenceDao buildSequenceDao(String tddlAppName, String tddlGroupName,
                                                    TddlSequenceProperties sequenceConfig, boolean init) {
        GroupSequenceDao sequenceDao = new GroupSequenceDao();
        // appName，必填
        sequenceDao.setAppName(tddlAppName);
        // 数据源的个数
        sequenceDao.setDscount(sequenceConfig.getDsCount());
        // 数据库名：从哪些数据库中取ID
        // 如果在末尾插入"-OFF",该源将被关掉，该源占据的SQL段会被保留"
        // 当dbGroupKeys中配置的个数小于dbcount的值的时候，默认配置了"-OFF"的源
        String[] tddlGroupNameSplit = StringUtils.split(tddlGroupName, MyossConstants.COMMA);
        String[] tddlGroupNameArray = org.springframework.util.StringUtils.trimArrayElements(tddlGroupNameSplit);
        sequenceDao.setDbGroupKeys(Lists.newArrayList(tddlGroupNameArray));
        // 内步长 ,默认为1000，取值在1-100000之间
        sequenceDao.setInnerStep(sequenceConfig.getInnerStep());
        // 重试次数，在多个groupDataSource的场景下，建议设置成1-2次。默认为2次
        sequenceDao.setRetryTimes(sequenceConfig.getRetryTimes());
        // 使用的表的表名 ，默认为sequence
        sequenceDao.setTableName(sequenceConfig.getTableName());
        // id生成器的字段名,默认为name
        sequenceDao.setNameColumnName(sequenceConfig.getNameColumnName());
        // 存值的列的字段名,默认为value
        sequenceDao.setValueColumnName(sequenceConfig.getValueColumnName());
        // 存修改时间的字段名 ,默认为gmt_modified
        sequenceDao.setGmtModifiedColumnName(sequenceConfig.getGmtModifiedColumnName());
        sequenceDao.setAdjust(sequenceConfig.isAdjust());

        if (init) {
            try {
                sequenceDao.init();
            } catch (SequenceException ex) {
                throw new BizRuntimeException("init TDDL GroupSequenceDao failed, tddlAppName: " + tddlAppName
                        + ", tddlGroupName: " + tddlGroupName + ", properties: " + sequenceConfig, ex);
            }
        }
        return sequenceDao;
    }

    /**
     * 构建 TDDL GroupSequenceDao 实例对象
     *
     * @param properties TDDL 属性配置
     * @param init 是否进行初始化
     * @return TDDL GroupSequenceDao 实例对象
     */
    public static GroupSequenceDao buildSequenceDao(TddlProperties properties, boolean init) {
        String tddlAppName = properties.getAppName();
        String tddlGroupName = properties.getGroupName();
        TddlSequenceProperties sequenceConfig = properties.getSequenceConfig();
        return buildSequenceDao(tddlAppName, tddlGroupName, sequenceConfig, init);
    }

    /**
     * 构建 TDDL GroupSequenceDao 实例对象
     *
     * @param properties TDDL 属性配置
     * @param init 是否进行初始化
     * @return TDDL GroupSequenceDao 实例对象
     */
    public static GroupSequenceDao buildSequenceDao(TddlProperty properties, boolean init) {
        String tddlAppName = properties.getAppName();
        String tddlGroupName = properties.getGroupName();
        TddlSequenceProperties sequenceConfig = properties.getSequenceConfig();
        return buildSequenceDao(tddlAppName, tddlGroupName, sequenceConfig, init);
    }

    /**
     * 初始化 TDDL 序列生成器，并注册到 Spring Application Context 中
     *
     * @param sequences 待初始化的 Sequence 实例集合
     * @param groupSequenceDao TDDL GroupSequenceDao 实例对象
     * @param applicationContext Spring Application Context
     */
    public static void initTddlSequence(Collection<Sequence> sequences, GroupSequenceDao groupSequenceDao,
                                        ApplicationContext applicationContext) {
        Map<String, GroupSequence> map = new HashMap<>(16);
        for (Sequence value : sequences) {
            if (!(value instanceof TddlSequence)) {
                continue;
            }
            TddlSequence tddlSequence = (TddlSequence) value;
            if (tddlSequence.getSequence() != null) {
                continue;
            }
            TableSequence tableSequence = tddlSequence.getTableInfo().getTableSequence();
            String sequenceName = tableSequence.getSequenceName();
            if (StringUtils.isBlank(sequenceName)) {
                throw new NullPointerException("sequenceName is null, tableInfo: " + tddlSequence.getTableInfo());
            }
            if (map.containsKey(sequenceName)) {
                GroupSequence sequence = map.get(sequenceName);
                tddlSequence.setSequence(sequence);
                continue;
            }
            GroupSequence sequence = initTddlSequence(applicationContext, sequenceName, null, groupSequenceDao);
            if (sequence == null) {
                throw new BizRuntimeException("create TDDL sequence failed, sequenceName = " + sequenceName
                        + ", tableName = " + tddlSequence.getTableInfo().getTableName());
            }
            tddlSequence.setSequence(sequence);
            map.put(sequenceName, sequence);
        }
    }

    /**
     * 初始化 TDDL 序列生成器，并注册到 Spring Application Context 中
     *
     * @param applicationContext Spring Application Context
     * @param sequenceName 待初始化的 TDDL sequence 名称
     * @param groupSequenceDaoBeanName TDDL GroupSequenceDao 实例对象Bean Name【参数
     *            {@code groupSequenceDaoBeanName} 和 {@code groupSequenceDao}
     *            二选一】
     * @param groupSequenceDao TDDL GroupSequenceDao 实例对象【参数
     *            {@code groupSequenceDaoBeanName} 和 {@code groupSequenceDao}
     *            二选一】
     * @return 生成的 TDDL 序列生成器
     */
    public static GroupSequence initTddlSequence(ApplicationContext applicationContext, String sequenceName,
                                                 String groupSequenceDaoBeanName, GroupSequenceDao groupSequenceDao) {
        if (applicationContext.containsBean(sequenceName)) {
            return applicationContext.getBean(sequenceName, GroupSequence.class);
        }
        if (groupSequenceDao != null || StringUtils.isNotBlank(groupSequenceDaoBeanName)) {
            try {
                // 创建Bean
                DefaultListableBeanFactory beanFactory = (DefaultListableBeanFactory) applicationContext
                        .getAutowireCapableBeanFactory();
                BeanDefinitionBuilder beanDefinitionBuilder = BeanDefinitionBuilder
                        .genericBeanDefinition(GroupSequence.class);
                beanDefinitionBuilder.addPropertyValue("name", sequenceName);
                if (groupSequenceDao != null) {
                    beanDefinitionBuilder.addPropertyValue("sequenceDao", groupSequenceDao);
                } else {
                    beanDefinitionBuilder.addPropertyValue("sequenceDao",
                            new RuntimeBeanReference(groupSequenceDaoBeanName));
                }
                AbstractBeanDefinition beanDefinition = beanDefinitionBuilder.getRawBeanDefinition();
                beanDefinition.setInitMethodName("init");

                // 注册Bean
                log.info("register TDDL sequence: {}", sequenceName);
                beanFactory.registerBeanDefinition(sequenceName, beanDefinition);
                return applicationContext.getBean(sequenceName, GroupSequence.class);
            } catch (Exception ex) {
                throw new BizRuntimeException("create TDDL sequence failed, sequenceName = " + sequenceName, ex);
            }
        }
        return null;
    }
}
